package Model;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**d
 * Created by Romain DUTARTRE on 07/02/14.
 */
public class MonteCarloSimulation {

    String callPutFlag;
    double s;
    double x;
    double t;
    double r;
    double b;
    double v;
    int nSteps;
    int nSimulation;
    int nbThreads;

    public MonteCarloSimulation(String callPutFlag, double s,
                                     double x, double t, double r,
                                     double b, double v, int nSteps,
                                     int nSimulation) {

        this.callPutFlag = callPutFlag;
        this.s = s;
        this.x = x;
        this.t = t;
        this.r = r;
        this.b = b;
        this.v = v;
        this.nSteps = nSteps;
        this.nSimulation = nSimulation;


    }

    public double MonteCarloStandardOption() {

        double dt, st, sum = 0, drift, vSqrdt;
        int z = 0;
        Random rand = new Random();

        dt = t / nSteps;
        drift = (b - v * v / 2) * dt;
        vSqrdt = v * Math.sqrt(dt);

        if (callPutFlag.contentEquals("call"))
            z = 1;
        else if (callPutFlag.contentEquals("put"))
            z = -1;
        for (int i = 1; i <= nSimulation; i++) {
            st = s;
            for (int j = 1; j <= nSteps; j++) {
                st = st * Math.exp(drift + vSqrdt * rand.nextGaussian());
            }
            sum = sum + Math.max(z * (st - x), 0);
        }
        return ( Math.exp(-r * t) * (sum / nSimulation) );
    }

    public double MonteCarloStandardOptionMultiThread() throws Exception {

        double tempResult = 0;

        // We get the number of threads
        nbThreads = (int) (Runtime.getRuntime().availableProcessors() / 0.9) ;
        ExecutorService poolOfThreads = Executors.newFixedThreadPool(nbThreads);

        // We initialize a set to get the result of each threads
        Set<Future<Double>> set = new HashSet<Future<Double>>();
        MonteCarloCallable algo = new MonteCarloCallable();

        // Then we run the algorithm
        for(int i = 0; i< nbThreads; i++) {
            Future<Double> algoResult = poolOfThreads.submit(algo);
            set.add(algoResult);
        }

        // We calculate the temporary result
        for(Future<Double> f : set) {
            tempResult += f.get();
        }

        // We shutdown the pool of threads
        poolOfThreads.shutdown();

        // We divide the temporary result by the nb of threads to get the final result
        return tempResult / nbThreads;
    }

    public class MonteCarloCallable implements Callable<Double> {

        @Override
        public Double call() throws Exception {

            double dt, st, sum = 0, drift, vSqrdt;
            int z = 0;
            Random rand = new Random();

            // This is the only change from the singlethread version
            nSimulation /= nbThreads ;

            dt = t / nSteps;
            drift = (b - v * v / 2) * dt;
            vSqrdt = v * Math.sqrt(dt);

            if (callPutFlag.contentEquals("call"))
                z = 1;
            else if (callPutFlag.contentEquals("put"))
                z = -1;

            for (int i = 1; i <= nSimulation; i++) {
                st = s;
                for (int j = 1; j <= nSteps; j++) {
                    st = st * Math.exp(drift + vSqrdt * rand.nextGaussian());
                }
                sum = sum + Math.max(z * (st - x), 0);
            }
            return ( Math.exp(-r * t) * (sum / nSimulation) );

        }
    }

}
